#include "arp.h"

struct hwa_info *hwlist, *hweth0;
int ifcount;
int *if_idx;
int			*pfd;
struct sockaddr_ll	*pfa;
arp_list *arp_head;

void print_mac(unsigned char *mac) 
{
	printf("%02x:%02x:%02x:%02x:%02x:%02x",
		mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
}

/* Insert the arp bode at the begining of the list */
void insert_arp_node(arp_list **head, arp_list *node)
{
	assert(head && node);
	if (*head == NULL) 
		node->next = NULL;
	else
		node->next = *head;
	*head = node;
}	

/* Create a new node given all the required info for initializing it
 * and the insert the node into the list 
 */
void create_arp_node(arp_list **head, arp_entry *aentry)
{
	arp_list *newn = (arp_list *)malloc(sizeof(arp_list));
	assert(newn);
	memcpy(&newn->aentry, aentry, sizeof(arp_entry)); 

	insert_arp_node(head, newn);
}

void update_arp_node(arp_list **head, arp_list *node, arp_pkt *apkt, int ifidx)
{
	assert(apkt);
	arp_list *newn = node;
	if (newn == NULL) {
		newn = (arp_list *)malloc(sizeof(arp_list));
		memset(newn, 0, sizeof(arp_list));
		memcpy(newn->aentry.ip, apkt->sender_ip, INET_ADDRSTRLEN);
		insert_arp_node(head, newn);
	}
	assert(newn);	
	
	memcpy(newn->aentry.hwaddr.addr, apkt->sender_mac.addr, ETH_ALEN);
	newn->aentry.ifindex = ifidx;
	newn->aentry.hatype = 1;
}

/* free its memory */
void free_arp_node(struct arp_list *node)
{
	assert(node);
	node->next = NULL;
	free(node);
}

/* Delete an arp node from the list given the IP addr */
arp_list *del_arp_node(struct arp_list **head, char *ipaddr, int confd)
{
	assert(head && *head);
	arp_list *tmp = *head;
	arp_list *prev = *head;

	assert(ipaddr || confd > 0);
	for (; tmp != NULL; tmp = tmp->next) {
		if (ipaddr && memcmp(ipaddr, tmp->aentry.ip, INET_ADDRSTRLEN) == 0) 	
			break;
		else if (confd > 0 && confd == tmp->aentry.confd)
			break;
	
		prev = tmp;
	}
	
	if (prev == tmp) {
		/* deleting the head */
		assert(tmp == *head);
		*head = tmp->next;
	} else if (tmp != NULL) {
		/* found the node to be deleted later in the list */
		prev->next = tmp->next;
	}

	return tmp;
}

/* Find the arp node based on the IP addr given */
arp_list *find_arp_node(arp_list *head, char *ipaddr)
{
	arp_list *tmp = head;
      	for (; tmp != NULL; tmp = tmp->next)
		if (memcmp(ipaddr, tmp->aentry.ip, INET_ADDRSTRLEN) == 0)
			break;

	return tmp;
}

/* Insert the hw node at the begining of the list */
void insert_begin(struct hwa_info *node)
{
	assert(node);
	if (hwlist == NULL) 
		node->hwa_next = NULL;
	else
		node->hwa_next = hwlist;
	hwlist = node;
}

/* Find the interface node given the ip */
struct hwa_info* find_hw_node(char *ipaddr) 
{
	assert(hwlist);
	struct hwa_info *tmp = hwlist;
      	for (; tmp != NULL; tmp = tmp->hwa_next)
		if (memcmp(ipaddr, tmp->ip, INET_ADDRSTRLEN) == 0)
			break;

	return tmp;

}

/* Populate the hwlist for eth0 interfaces */
void populate_eth0_hw(bool printout)
{
	struct hwa_info		*hwa, *hwptr, *hwhead;
	struct sockaddr_in	*sa;
	char			*ptr;
	int			i, prflag, pfd[10], count = 0;

	hwhead = Get_hw_addrs();
	for (hwa = hwhead; hwa != NULL; hwa = hwa->hwa_next) {
		/* check eth0 and its aliases */
		if (strncmp(hwa->if_name, "eth0", 4) == 0) {
				
        		hwptr = (struct hwa_info *) Calloc(1, sizeof(struct hwa_info));
			memcpy (hwptr, hwa, sizeof(struct hwa_info));
				
			insert_begin(hwptr);	
			
			if (strcmp(hwa->if_name, "eth0") == 0) 
				hweth0 = hwptr;
		
			memset(hwptr->ip, 0, INET_ADDRSTRLEN);	
			sa = (struct sockaddr_in *)hwptr->ip_addr;
			Inet_ntop(AF_INET, &sa->sin_addr, hwptr->ip, INET_ADDRSTRLEN);
			//print the interface details
			if (printout) {
				printf("Interface %s(name) %d(idx)\n", hwa->if_name, hwa->if_index);
				printf("\tInterface HW addr: ");
				print_mac(hwa->if_haddr);
				printf("\n");
				printf("\tInterface IP Address: %s\n", hwptr->ip);
			}
			ifcount++;
		}
	}
	free_hwa_info(hwhead);
}

/*
 *Create and bind the PF packet sockets
 */
void sock_crbnd(int *pfd, struct sockaddr_ll *pfa, int *maxpfd)
{
	int idx = 0;
	struct hwa_info *iter;

	assert(hwlist);
	
	for (iter = hwlist; iter != NULL; iter = iter->hwa_next) {
		//create the raw socket
		pfd[idx] = Socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ARP23));

		if (*maxpfd < pfd[idx])
			*maxpfd = pfd[idx];

		//bind it to the current interface
		pfa[idx].sll_family = PF_PACKET;
		pfa[idx].sll_protocol = htons(ETH_P_ARP23);
		pfa[idx].sll_ifindex = iter->if_index;

		Bind(pfd[idx], (SA*) &pfa[idx], sizeof(pfa[idx]));
		//update if idx
		if_idx[idx] = iter->if_index;
		
		idx++;
	}
	iter = NULL;
}

int send_frame(char *buffer, int flen, int if_idx_idx)
{
	//find the fd & addr from the if_idx
	int idx;
	struct sockaddr_ll sal;
	
	for(idx = 0; idx < ifcount; idx++)
	{
		if(if_idx[idx] == if_idx_idx)
			break;
	}

	assert(idx < ifcount);
	
	memcpy(&sal, &pfa[idx], sizeof(pfa[idx]));

	sal.sll_hatype = ARPHRD_ETHER;
	sal.sll_pkttype = PACKET_HOST;
	sal.sll_halen = ETH_ALEN;
	memcpy(&sal.sll_addr, buffer, ETH_ALEN);
	int rc = sendto(pfd[idx], buffer, flen, 0, (SA*) &sal, sizeof(sal));
	return rc;
}

/* Broadcast the ARP packet on all the interface */
void broadcast_arp_pkt(struct hwa_info *hw, char *pkt) 
{
	char buffer[ETH_FRAME_LEN + ETH_FCS_LEN];
	int rc = 0;

	memset(buffer, 0, sizeof(buffer));	
	/* Broadcast MAC address*/
	unsigned  char b_dst[ETH_ALEN] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
	
	eth_pkt *epkt = (eth_pkt *)buffer;
	arp_pkt *apkt = &epkt->apkt;
	struct ethhdr *ehdr = &epkt->ehdr;

	memcpy(ehdr->h_dest, b_dst, ETH_ALEN);
	memcpy(ehdr->h_source, hw->if_haddr, ETH_ALEN);
	ehdr->h_proto = htons(ETH_P_ARP23);
	
	apkt->arpid = ARP_ID;
	apkt->htype = htons(0x1);
	apkt->ptype = htons(0x800);
	apkt->hlen = 0x6;
	apkt->plen = 0x4;
	apkt->op = htons(ARPOP_REQUEST);
	memcpy(apkt->sender_mac.addr, hw->if_haddr, ETH_ALEN);
	memcpy(apkt->sender_ip, hw->ip, INET_ADDRSTRLEN);
	memset(apkt->target_mac.addr, 0xFF, ETH_ALEN);
	memcpy(apkt->target_ip, pkt, INET_ADDRSTRLEN);
	
	rc = send_frame(buffer, ETH_FRAME_LEN, hw->if_index);
	assert(rc);

	printf("Broadcast Done\n");
}

int areq(struct in_addr *ipaddr, socklen_t sockaddrlen, arp_hwaddr *addr) 
{
	struct sockaddr_un arpaddr, cliaddr;
	int rc = 0;

	memset(&cliaddr, 0, sizeof(cliaddr));
	memset(&arpaddr, 0, sizeof(arpaddr));
	arpaddr.sun_family = AF_LOCAL;
	strcpy(arpaddr.sun_path, ARP_SUNPATH);


	int sockfd = Socket(AF_LOCAL, SOCK_STREAM, 0);

	//generate a temp sunpath
	char tmplPath[UNIX_PATH_MAX];
	strncpy(tmplPath, CLI_SUNPATH_TMPL, sizeof(tmplPath));
	
	int tmpfd = mkstemp(tmplPath);
	Close(tmpfd);
	unlink(tmplPath);

	cliaddr.sun_family = AF_LOCAL;
	strcpy(cliaddr.sun_path, tmplPath);
	
	Bind(sockfd, (struct sockaddr*) &cliaddr, sizeof(struct sockaddr_un));
	rc = connect(sockfd, (struct sockaddr *) &arpaddr, sizeof(struct sockaddr_un));
	if (rc < 0) {
		perror("Error connecting to ARP UD socket ");
		goto out;	
	}
	char ip[INET_ADDRSTRLEN];
	memset(ip, 0, INET_ADDRSTRLEN);

	Inet_ntop(AF_INET, ipaddr, ip, INET_ADDRSTRLEN);
	int nwrite = write(sockfd, ip, INET_ADDRSTRLEN);
	if (nwrite <= 0) {
		perror("AREQ: Error wrtiting to ARP UD socket: ");
		rc = -1;
		goto out;
	}
	
	// printf("Areq: Ipaddr %s, sent to the arp module\n", ip);
	fd_set rset;
	struct timeval tv;

	FD_ZERO(&rset);
	FD_SET(sockfd, &rset);

	tv.tv_sec = 5;
	tv.tv_usec = 0;

	Select(sockfd + 1, &rset, NULL, NULL, &tv);
	if (!FD_ISSET(sockfd, &rset)) {
		printf("AREQ: Time out reading ARP Module socket\n");
		rc = -1;
		goto out;
	}
	
	// printf("AREQ: reading ARP Module socket\n");
		
	int nread = read(sockfd, addr, sizeof(arp_hwaddr));
	if (nread <= 0) {
		perror("AREQ: Error reading from ARP UD socket: ");
		rc = -1;
		goto out;
	}
out:
	close(sockfd);
	return rc;
}
